package org.third.security.cert;


import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.PKIXCertPathValidatorResult;
import java.security.cert.PKIXParameters;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERUTF8String;
//import org.bouncycastle.asn1.ASN1InputStream;
//import org.bouncycastle.asn1.ASN1Sequence;
//import org.bouncycastle.asn1.ASN1TaggedObject;
//import org.bouncycastle.asn1.DERObject;
//import org.bouncycastle.asn1.DERUTF8String;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.third.common.exception.InvalidCertificateException;
import org.third.common.utils.StreamUtils;

public class X509CertificateTest {
    private static final Logger logger = LoggerFactory.getLogger(CustomX509PrincipalExtractor.class);
    private static KeyStore trustStore;
    static {
        InputStream inputStream = null;
        try {
            inputStream = X509CertificateTest.class.getResourceAsStream("tomcat.jks");
            trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            char[] password = "tomcat1".toCharArray();
            trustStore.load(inputStream, password);
        } catch (Exception e) {
            logger.error("IOException with  trustStoreResource", e);
        } finally {
            StreamUtils.closeQuietly(inputStream);
        }
    }

    public static void main(String[] args) throws Exception {
        X509Certificate clientCert = parseStringAsX509();
        parseSubject_and_SAN(clientCert);
    }

    private boolean validateHostList(String hostname) {
        Pattern pattern = Pattern.compile("cn=(.*),", Pattern.CASE_INSENSITIVE);
        Set<String> trustedHostList = new HashSet<>();
        return trustedHostList.contains(pattern.matcher(hostname).group(1));
    }

    private boolean validateCertificate(X509Certificate certificate) throws InvalidCertificateException {
        if (certificate == null) {
            logger.error("No X509Certificate provided");
            return false;
        }
        try {
            certificate.checkValidity();

            String hostname = certificate.getSubjectDN().toString();
            if (!validateHostList(hostname)) {
                logger.error("Invalid Hosts");
                logger.trace("certficate subject dn :" + certificate.getSubjectDN());
                return false;
            }
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            List<X509Certificate> mylist = new ArrayList<X509Certificate>();
            mylist.add(certificate);
            CertPath cp = cf.generateCertPath(mylist);

            PKIXParameters params = new PKIXParameters(trustStore);
            params.setRevocationEnabled(false);
            CertPathValidator cpv = CertPathValidator.getInstance(CertPathValidator.getDefaultType());
            PKIXCertPathValidatorResult pkixCertPathValidatorResult = (PKIXCertPathValidatorResult) cpv.validate(cp, params);
            if (pkixCertPathValidatorResult != null)
                return true;
        } catch (CertificateExpiredException e) {
            logger.trace("Certificate expired", e);
            throw new InvalidCertificateException(e);
        } catch (CertificateNotYetValidException e) {
            logger.trace("Certificate not yet valid", e);
            throw new InvalidCertificateException(e);
        } catch (CertificateException e) {
            logger.trace("Certificate exception", e);
            throw new InvalidCertificateException(e);
        } catch (CertPathValidatorException e) {
            logger.trace("Certificate path validator exception", e);
            throw new InvalidCertificateException(e);
        } catch (NoSuchAlgorithmException e) {
            logger.trace("No such algorithm", e);
            throw new InvalidCertificateException(e);
        } catch (KeyStoreException e) {
            logger.trace("KeyStore exception", e);
            throw new InvalidCertificateException(e);
        } catch (InvalidAlgorithmParameterException e) {
            logger.trace("Invalid algorithm parameter exception", e);
            throw new InvalidCertificateException(e);
        }
        return false;
    }

    static String getStringFromASNDerEncodedByteArray(byte[] byteArray) {
        if (logger.isDebugEnabled()) {
            logger.debug("getStringFromASNDerEncodedByteArray(byte[]) - start");
        }

        String ret = null;
        ASN1InputStream asn1InputStream = null;
        try {
            asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(byteArray));
            ASN1Primitive derObject = asn1InputStream.readObject();
            ASN1Sequence asn1Sequence = ASN1Sequence.getInstance(derObject);
            Object objectValue = asn1Sequence.getObjectAt(1);
            if (objectValue instanceof ASN1TaggedObject) {
                ASN1TaggedObject asn1TaggedObject = (ASN1TaggedObject) objectValue;
                try {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Try to get string from DERUTF8String.");
                    }
                    ASN1Primitive derTaggedObject = asn1TaggedObject.getObject();
                    DERUTF8String derUtf8String = DERUTF8String.getInstance(derTaggedObject);
                    ret = derUtf8String.getString();
                } catch (IllegalArgumentException e) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("VALIDATION - Can ot get String From DERUTF8String, [" + e.getMessage() + "].", e);
                    }
                    // DERBitString ???
                    // DERBitString derBitString = DERBitString.getInstance(asn1TaggedObject.getObject());
                    // ret = derBitString.getString();

                    // DERIA5String
                    // DERUniversalString
                    // DERBMPString
                    // DERVisibleString
                    // DERPrintableString
                    // DERT61String
                    // DERGeneralString
                    // DERNumericString
                }
            }
        } catch (Exception e) {
            if (logger.isInfoEnabled()) {
                logger.info("VALIDATION - Cannot get String From ASNDerEncoded ByteArray, [" + e.getMessage() + "].");
            }
        } finally {
            StreamUtils.closeQuietly(asn1InputStream);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("VALIDATION - getStringFromASNDerEncodedByteArray(byte[]) - end. Ret is [" + ret + "].");
        }
        return ret;

    }

    public static void parseSubject_and_SAN(X509Certificate clientCert) throws Exception {

        if (clientCert == null) {
            // String certificateName = "C:/Users/gongyo/cn2.cer";
            FileInputStream fileInputStream = new FileInputStream(X509CertificateTest.class.getResource("cn2.cer").getPath());
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
            clientCert = (X509Certificate) certificateFactory.generateCertificate(fileInputStream);
        }

        CustomX509PrincipalExtractor extractor = new CustomX509PrincipalExtractor();
        extractor.x509Attribute = "subjectDN";
        extractor.setRegex("l=(.*?),");
        extractor.extractPrincipal(clientCert);

        extractor.x509Attribute = "san";
        extractor.otherNameUserPrincipalNameExtractor = new CustomX509UserPrincipalNameExtractor();
        extractor.sanType = SubjectAlternativeNameGeneralNames.otherName.name();
        extractor.extractPrincipal(clientCert);
    }

    public static X509Certificate parseStringAsX509() {

        // byte[] certificateData = HttpServletRequest.getHeader("javax.servlet.request.X509Certificate").getBytes();
        String certificateData = "-----BEGIN CERTIFICATE-----\r\n" + "MIIEATCCAumgAwIBAgIEWr3CtDANBgkqhkiG9w0BAQsFADBRMQswCQYDVQQGEwJD\r\n"
                + "bjEMMAoGA1UECAwDU1QxMQswCQYDVQQHDAJMMTELMAkGA1UECgwCbzExDDAKBgNV\r\n"
                + "BAsMA291MTEMMAoGA1UEAwwDY24xMB4XDTE4MDMzMDA0NTMwOFoXDTE5MDMzMDA0\r\n"
                + "NTMwOFowUTELMAkGA1UEBhMCQ24xDDAKBgNVBAgMA1NUMTELMAkGA1UEBwwCTDEx\r\n"
                + "CzAJBgNVBAoMAm8xMQwwCgYDVQQLDANvdTExDDAKBgNVBAMMA2NuMTCCASIwDQYJ\r\n"
                + "KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKBKaE5Rk6Qr7GP/M5LvjnwRnkTptoPu\r\n"
                + "3jMx9KEHKVFMw8vc5+0RGRPNh2g/8OsjeRLmIJh+ysn+qytsjamw81mXqYFtFhS+\r\n"
                + "M/OBOIRJB02vscztn2Uai2LSwXROH9ItdD5EpoH/28E6d+XC5hwRP5KmdCwTn+uY\r\n"
                + "OHBGgTERFh2MZZxx2aFAYT6sMQA+VAtKw1ES9mgxiC08MUp38ipgtTkqYoCJxF/y\r\n"
                + "1O670gfsQINnQNauCUb3X2mbzRM0JiTbNvY9LT/jeAwJ+abPbJePpfE9wYcJmpby\r\n"
                + "yY4eg9tgPv9mF9o4KWJ7D8BBYY9aI1fEhFzjNYxabLnZPlyJmycq44cCAwEAAaOB\r\n"
                + "4DCB3TCB2gYDVR0RBIHSMIHPpIGBMH8xDjAMBgNVBAUTBXNuMDAxMRwwGgYJKoZI\r\n"
                + "hvcNAQkBFg10ZXN0QHRlc3QuY29tMQswCQYDVQQGEwJ1czEMMAoGA1UECAwDU1Qy\r\n"
                + "MQswCQYDVQQHDAJMMjELMAkGA1UECgwCbzIxDDAKBgNVBAsMA291MjEMMAoGA1UE\r\n"
                + "AwwDY24yggt3dy50ZXN0LmNvbYcEwKgABaAUBgorBgEEAYI3FAIDoAYMBHVwbjGB\r\n"
                + "C3JmYzgyMm5hbWUyhhNodHRwczovL3d3dy5hYmMuY29tMA0GCSqGSIb3DQEBCwUA\r\n"
                + "A4IBAQB4++G/OGaaGimuNDemmUVsmGEvnLEFySPSt+A0vLba6roetu7qikVX5Szq\r\n"
                + "9G+bDw9oikYvJLrGanou2uIxJQKI0kLfjzqoQZCVfG1KyLdvdd6fgrQex7sDhLMc\r\n"
                + "hCvB7MuspIBaAZoacTs8D6d0BcR/BVeu/9ou1bdUw+iW/76SolZ9UkAqSjmjVKlA\r\n"
                + "xfrBTarqh3noCLFKtAfg/Io5Rd//zJnyh/d8ss9rQIfU+lJkuhEahVQfepY0uMIM\r\n"
                + "/uOwRkLoBuo98ijDAY1e7l+XoQZ+hoY+hKaa5L9hNA9dfeOe6iaKHK+XyBQ7MiuS\r\n" + "6gTN3nfUFV7e43DpG+ZND7FDOszl\r\n"
                + "-----END CERTIFICATE-----\r\n" + "";

        ByteArrayInputStream certificateInputStream = new ByteArrayInputStream(certificateData.getBytes());
        X509Certificate certificates[] = null;
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(certificateInputStream);
            certificates = new X509Certificate[1];
            certificates[0] = x509Certificate;
            return certificates[0];
        } catch (Exception e) {
            // throw new HpSsoBaseException("Failed to parse client certificate",
            // HpSsoErrorCode.VALIDATION_CLIENT_CERTIFICATE, e);
            // throw new HpSsoBaseException("Client certificate flow general error",
            // HpSsoErrorCode.VALIDATION_CLIENT_CERTIFICATE, e);
        }
        return null;

    }

}